# -*- coding: utf-8 -*-
# @Time    : 2021/11/2 21:30
# @Author  : BrandonGT
# @FileName: main_window.py

# Python 自带Module
import logging
import os
import sys
import ctypes
import shutil
import inspect
import hashlib
import datetime
import threading
import subprocess
from getpass import getuser

# Python 第三方Module
import yaml
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

# Python 自定义Module
from frame import bd_pyqt5 as BPyQt5
from bin.bd_logging import BLogger
from bin.function import ApplicationSearch as AppSearch
from bin.function import Updator

# ------------------ 定义log ------------------ 开始
with open("../config/config.yaml", "r", encoding="utf-8") as content:
    _config = yaml.load(content, Loader=yaml.Loader)
_LOGGER = _config.get("BLogger", {})
_LOGGER_NAME = _LOGGER.get("name", "Default")
_LOGGER_LEVEL = _LOGGER.get("level", 10)
_IS_FILE = _LOGGER.get("file", True)
_FILE_LEVEL = _LOGGER.get("file_level", 0)
_IS_CONSOLE = _LOGGER.get("console", True)
_PATH = _LOGGER.get("path", "D:/")
logger = BLogger(_LOGGER_NAME, level=_LOGGER_LEVEL, file=_IS_FILE, file_level=_FILE_LEVEL, console=_IS_CONSOLE, path=_PATH)
# ------------------ 定义log ------------------ 结束

myappid = 'mycompany.myproduct.subproduct.version' # arbitrary string
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)


class StatusBar(BPyQt5.BQWidget):
    """顶部状态栏"""
    def __init__(self):
        super(StatusBar, self).__init__()
        self.setObjectName("StatusBar")
        self.setFixedSize(900, 65)
        self.build_ui()

    def build_ui(self):
        """构建顶部状态栏UI"""
        # 状态栏主体Layout，QHBOX
        main_layout = BPyQt5.BQHBoxLayout()
        main_layout.setContentsMargins(10, 0, 0, 0)
        main_layout.setAlignment(Qt.AlignLeft)
        self.setLayout(main_layout)

        # 项目选择按钮
        project_btn = QPushButton()
        project_btn.setObjectName("ProjectButton")
        project_btn.setFixedSize(50, 50)

        # 添加控件至Layout
        main_layout.addWidget(project_btn)


class ApplicationSection(BPyQt5.BQWidget):
    """软件选择区"""
    def __init__(self):
        super(ApplicationSection, self).__init__()
        self.setObjectName("ApplicationSection")
        self.order = False  # True:正序 False：倒序
        self.build_ui()
        self.app_sort()

    def build_ui(self):
        """构建软件选择区的UI"""
        # 软件区主体Layout，QVBOX
        main_layout = BPyQt5.BQVBoxLayout()
        main_layout.setAlignment(Qt.AlignTop)
        self.setLayout(main_layout)

        # 顶部显示类型的状态栏
        types_layout = BPyQt5.BQHBoxLayout()
        types_bar = BPyQt5.BQWidget()
        types_bar.setLayout(types_layout)

        application_btn = QPushButton("application")
        application_btn.setObjectName("AppTypes")
        version_btn = QPushButton("version")
        version_btn.setObjectName("AppVersion")
        application_btn.clicked.connect(self.app_sort)

        # 添加控件至Layout并添加至主Layout
        types_layout.addWidget(application_btn, 7)
        types_layout.addWidget(version_btn, 3)
        main_layout.addWidget(types_bar)

        # 创建用来展示APP的list控件
        self.app_list = BPyQt5.BQListWidget()
        self.app_list.setObjectName("ListApp")

        # 添加list控件至主Layout中
        main_layout.addWidget(self.app_list)

        # 增加刷新按钮，不添加layout，直接定位
        self.btn_refresh = QPushButton()
        self.btn_refresh.setObjectName("RefreshButton")
        self.btn_refresh.setAttribute(Qt.WA_StyledBackground)
        self.btn_refresh.raise_()
        self.btn_refresh.setParent(application_btn)
        self.btn_refresh.setFixedSize(30, 30)
        self.btn_refresh.move(410, 2)

        # 增加本地化按钮，不添加layout，直接定位
        self.btn_localize = QPushButton()
        self.btn_localize.setText("本地化")
        self.btn_localize.setObjectName("LocalizeButton")
        self.btn_localize.setAttribute(Qt.WA_StyledBackground)
        self.btn_localize.raise_()
        self.btn_localize.setParent(application_btn)
        self.btn_localize.setFixedSize(60, 30)
        self.btn_localize.move(350, 2)

    def app_sort(self):
        """App列表排序，可以通过点击切换正序和倒序"""
        widgets = {}
        texts = []
        count = self.app_list.count()
        for i in range(count):
            widgets[self.app_list.item(i).text()] = self.app_list.item(i)
            texts.append(self.app_list.item(i).text())
        if self.order:
            texts.sort(reverse=True)
            self.order = False
        else:
            texts.sort()
            self.order = True
        for i in range(count):
            self.app_list.takeItem(0)
        id = 0
        while id < len(texts):
            a = widgets[texts[id]]
            self.app_list.addItem(a)
            id += 1


class PluginSection(BPyQt5.BQWidget):
    """插件版本选择区"""
    def __init__(self):
        super(PluginSection, self).__init__()
        self.setObjectName("PluginSection")
        self.row = 0
        self.build_ui()

    def build_ui(self):
        """构建插件版本区UI"""
        # 插件区主体Layout，QVBOX
        main_layout = BPyQt5.BQVBoxLayout()
        main_layout.setAlignment(Qt.AlignTop)
        self.setLayout(main_layout)

        # 顶部装填栏
        title_bar = QPushButton("App")
        title_bar.setFixedSize(270, 30)
        title_bar.setObjectName("PluginTitleBar")

        # 插件选择区域
        self.plugin_layout = QGridLayout()
        self.plugin_layout.setSpacing(10)
        self.plugin_layout.setContentsMargins(5, 12, 5, 5)

        # 添加控件至Layout
        main_layout.addWidget(title_bar)
        main_layout.addLayout(self.plugin_layout)


class FunctionSection(BPyQt5.BQWidget):
    """备注和启动区"""
    def __init__(self):
        super(FunctionSection, self).__init__()
        self.setObjectName("FunctionSection")
        self.start_mode = 2  # 2:正常启动  3:清洁启动 4:清洁启动(保留工具架)
        self.build_ui()

    def build_ui(self):
        """构建备注启动区UI"""
        # 功能区主体Layout，QVBOX
        main_layout = BPyQt5.BQVBoxLayout()
        main_layout.setAlignment(Qt.AlignTop)
        self.setLayout(main_layout)

        # 描述栏
        description_bar = QLabel("Description")
        description_bar.setObjectName("DescriptionBar")
        description_bar.setMaximumHeight(30)

        # 具体描述
        self.description_content = QPlainTextEdit()
        self.description_content.setReadOnly(True)
        self.description_content.setObjectName("DescriptionContent")

        # 启动按钮
        self.startup_btn = QPushButton("Launch")
        self.startup_btn.setObjectName("StartButton")
        self.startup_btn.setMinimumHeight(30)

        # 添加控件至Layout
        main_layout.addWidget(description_bar, 2)  # 描述栏标题
        main_layout.addWidget(self.description_content, 5)  # 描述栏内容
        main_layout.addWidget(self.startup_btn)  # 启动按钮

    def mousePressEvent(self, evt) -> None:
        """改写鼠标点击事件，用来响应右键点击Launcher按钮弹出新的窗口选择启动软件的模式"""
        if evt.button() == 2:  # 检测是否按了右键
            mouse_gpos_x = evt.globalX()
            mouse_gpos_y = evt.globalY()
            self.launchmenu = LaunchMenu(mouse_gpos_x, mouse_gpos_y, self.start_mode)
            self.launchmenu.show()
            self.launchmenu.change_value.connect(self.change_start_mode)

        return super().mousePressEvent(evt)

    def change_start_mode(self, value):
        """改变启动选择的默认值"""
        value = abs(value)
        self.start_mode = value


class LaunchMenu(BPyQt5.BQWidget):
    """Launcher按钮的配置界面"""
    change_value = pyqtSignal(int)

    def __init__(self, pos_x, pos_y, mode):
        super(LaunchMenu, self).__init__()
        self.start_mode = mode  # 2:正常启动  3:清洁启动 4:清洁启动(保留工具架)
        self.setFixedSize(170, 70)
        self.move(pos_x, pos_y)
        self.setObjectName("LaunchButtonMenu")
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.build_ui()
        # 设置QSS样式表
        try:
            root = _config.get("Launcher").get("launcher_local_path")
            BPyQt5.BQssTool.set_qss(f"{root}frame/dcc_launcher.qss", self)
        except:
            root = eval(_config.get("Launcher").get("launcher_local_qss"))
            BPyQt5.BQssTool.set_qss(f"{root}", self)

    def build_ui(self):
        """构建UI"""
        main_layout = BPyQt5.BQVBoxLayout()
        main_layout.setAlignment(Qt.AlignCenter)
        self.setLayout(main_layout)

        # 添加两个单选框
        self.button_group = QButtonGroup()
        self.radbtn01 = QRadioButton("正常启动")
        self.radbtn02 = QRadioButton("清洁启动")
        self.radbtn03 = QRadioButton("清洁启动(保留工具架)")
        self.radbtn01.setObjectName("LaunchMenuButton")
        self.radbtn02.setObjectName("LaunchMenuButton")
        self.radbtn03.setObjectName("LaunchMenuButton")
        self.radbtn01.setFixedHeight(20)
        self.radbtn02.setFixedHeight(20)
        self.radbtn03.setFixedHeight(20)

        # 测试自定义信号
        self.button_group.buttonClicked.connect(self.button_toggled_handle)

        if self.start_mode == 2:
            self.radbtn01.setChecked(True)
        elif self.start_mode == 3:
            self.radbtn02.setChecked(True)
        elif self.start_mode == 4:
            self.radbtn03.setChecked(True)

        self.button_group.addButton(self.radbtn01)
        self.button_group.addButton(self.radbtn02)
        self.button_group.addButton(self.radbtn03)
        main_layout.addWidget(self.radbtn01)
        main_layout.addWidget(self.radbtn02)
        main_layout.addWidget(self.radbtn03)

    def leaveEvent(self, evt):
        """当鼠标离开窗口时，界面关闭"""
        self.close()
        return super().leaveEvent(evt)

    def button_toggled_handle(self, object):
        """按钮触发事件"""
        id = self.button_group.id(object)
        self.change_value.emit(id)


class LauncherConsole(BPyQt5.BQWidget, BPyQt5.BQPlainTextEditLogger):
    """启动器的控制台界面"""
    def __init__(self):
        super(LauncherConsole, self).__init__()
        self.build_ui()

    def build_ui(self):
        """构建控制台UI"""
        log_box = BPyQt5.BQPlainTextEditLogger()
        log_box.widget.setObjectName("Console")
        log_box.setFormatter(logging.Formatter(logger.FORMATTER, logger.DATE_FORMATTER))
        log_box.setLevel(logger.FILE_LEVEL)
        logger.addHandler(log_box)

        main_layout = BPyQt5.BQVBoxLayout()
        main_layout.addWidget(log_box.widget)

        self.setLayout(main_layout)


class LauncherSetting(BPyQt5.BQWidget):
    """启动器的设置界面"""
    def __init__(self):
        super(LauncherSetting, self).__init__()
        self.build_ui()

    def build_ui(self):
        """构建设置UI"""
        main_layout = QGridLayout()
        main_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        main_layout.setObjectName("Setting")
        self.setLayout(main_layout)

        # 添加package读取路径
        label_package_source = QLabel("软件包读取路径选择：")
        label_package_source.setFixedSize(150, 25)
        label_package_source.setObjectName("SettingLabel")

        local_or_server = _config.get("Launcher").get("package_local_or_network")
        local_package = eval(_config.get("Launcher").get("package_local_path"))
        server_package = eval(_config.get("Launcher").get("package_network_path"))
        local_package_label = "本地路径"
        server_package_label = "服务器路径"

        self.combox_package_source = BPyQt5.BQComboBox()
        self.combox_package_source.setFixedSize(150, 25)
        self.combox_package_source.setView(QListView())
        self.combox_package_source.setObjectName("SettingCombox")
        self.combox_package_source.store_path(local_package_label, local_package)
        self.combox_package_source.store_path(server_package_label, server_package)
        self.combox_package_source.addItem(server_package_label)
        self.combox_package_source.addItem(local_package_label)
        self.combox_package_source.setCurrentIndex(local_or_server)  # UI创建时默认使用服务器路径读取

        main_layout.addWidget(label_package_source, 0, 0)
        main_layout.addWidget(self.combox_package_source, 0, 1)


class LauncherUpdate(QDialog):
    """启动器版本检查提示框"""
    def __init__(self):
        super(LauncherUpdate, self).__init__()
        self.setWindowTitle("鹰角网络启动器版本检查")
        self.setWindowModality(Qt.WindowModal)
        self.setFixedSize(300, 100)
        self.setObjectName("LuancherUpdator")
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.updator = Updator()
        self.runcode = 0  # 0:仍然运行 1：更新
        self.build_ui()

        # 设置QSS样式表
        try:
            root = _config.get("Launcher").get("launcher_local_path")
            BPyQt5.BQssTool.set_qss(f"{root}frame/dcc_launcher.qss", self)
        except:
            root = eval(_config.get("Launcher").get("launcher_local_qss"))
            BPyQt5.BQssTool.set_qss(f"{root}", self)

    def build_ui(self):
        """构建UI"""
        main_layout = BPyQt5.BQVBoxLayout()
        main_layout.setAlignment(Qt.AlignCenter)
        self.setLayout(main_layout)

        # 创建版本对比的label
        local_label = QLabel()
        server_label = QLabel()
        local_label.setFixedHeight(35)
        server_label.setFixedHeight(35)
        local_label.setObjectName("UpdatorLabel")
        server_label.setObjectName("UpdatorLabel")
        local_label.setText(f"本地当前版本：{self.updator.get_actul_version()}")
        server_label.setText(f"服务器最新版本：{self.updator.get_actul_version(local=False)}")

        main_layout.addWidget(local_label)
        main_layout.addWidget(server_label)

        # 创建更新按钮和无视按钮
        sub_layout = BPyQt5.BQHBoxLayout()
        sub_layout.setAlignment(Qt.AlignCenter)
        main_layout.addLayout(sub_layout)

        # 创建两个按钮
        btn_update = QPushButton("更新")
        btn_ignore = QPushButton("仍然启动")
        btn_update.setObjectName("UpdatorButtonA")
        btn_ignore.setObjectName("UpdatorButtonB")
        btn_update.setFixedSize(100, 30)
        btn_ignore.setFixedSize(100, 30)

        btn_update.clicked.connect(self.do_update)
        btn_ignore.clicked.connect(lambda : self.close())

        sub_layout.addWidget(btn_update)
        sub_layout.addWidget(btn_ignore)

    def do_update(self):
        """更新"""
        self.runcode = 1
        self.close()

    def mouseMoveEvent(self, QMouseEvent):
        # 计算鼠标移动后，在世界坐标中的移动距离
        move_x = self.global_x - QMouseEvent.globalX()
        move_y = self.global_y - QMouseEvent.globalY()
        # 计算实际窗口需要移动的距离
        final_x = self.origin_x - move_x
        final_y = self.origin_y - move_y
        # 移动窗口 条件：如果点击的是左键
        if self.pressed == 1:
            self.move(final_x, final_y)

    def mousePressEvent(self, QMouseEvent):
        # 打开鼠标跟踪事件
        self.setMouseTracking(True)
        # 记录鼠标点击时的世界坐标
        self.global_x = QMouseEvent.globalX()
        self.global_y = QMouseEvent.globalY()
        # 记录鼠标点击时，窗口的世界坐标
        self.origin_x = self.x()
        self.origin_y = self.y()
        # 测试一下能够返回具体按的是哪个键
        self.pressed = QMouseEvent.button()

    def mouseReleaseEvent(self, QMouseEvent):
        self.setMouseTracking(False)


class LauncherLocalizePanel(BPyQt5.QWidget):
    """启动器本地化进度显示面板"""
    def __init__(self):
        super(LauncherLocalizePanel, self).__init__()
        self.LOCAL = eval(_config.get("Launcher").get("package_local_path"))
        self.NETWORK = eval(_config.get("Launcher").get("package_network_path"))
        print(self.LOCAL)
        print(self.NETWORK)
        self.setMinimumSize(1080, 400)
        self.app_search = AppSearch()
        self.build_ui()
        self.copy_setup()

    def build_ui(self):
        """构建UI"""
        main_layout = BPyQt5.BQVBoxLayout()
        main_layout.setAlignment(Qt.AlignCenter)
        self.setLayout(main_layout)

        # 创建进度条
        sub_layout = BPyQt5.BQHBoxLayout()
        main_layout.addLayout(sub_layout)

        self.label_progres = QLabel()
        sub_layout.addWidget(self.label_progres)

        self.progress_bar = QProgressBar()
        sub_layout.addWidget(self.progress_bar)

        self.text_progress = QTextEdit()
        main_layout.addWidget(self.text_progress)

    def main(self):
        self.show()

    def copy_setup(self):

        dcc_packages = self.app_search.get_final_apps(types='dcc', ver='single')
        plugin_packages = self.app_search.get_final_apps(types='plugin', ver='all')
        packages = dcc_packages + plugin_packages

        try:
            shutil.rmtree(self.LOCAL)
            logger.info("清空本地Package历史信息...")
        except FileNotFoundError:
            logger.info("本地Package不存在...")

        self.items = len(packages)
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(self.items)
        self.CopyThread = CopyThreading(items_list=packages, items=self.items,
                                        local=self.LOCAL, network=self.NETWORK)
        self.CopyThread.items_signal.connect(self.callback_logger)
        self.CopyThread.start_signal.connect(self.callback_starter)

        self.CopyThread.start()

    def callback_logger(self, items_signal_list):
        """刷新面板回调"""
        self.progress_bar.setValue(items_signal_list[0])
        self.label_progres.setText(u"当前进度：%d/%d" % (items_signal_list[0], self.items))
        self.text_progress.append(items_signal_list[1])

    def callback_starter(self, start_signal):
        """拷贝启动"""
        self.text_progress.append(start_signal)


class CopyThreading(threading.Thread, QObject):
    """多线程拷贝基类"""
    items_signal = pyqtSignal(list)
    start_signal = pyqtSignal(str)

    def __init__(self, items_list=None, items=None, local=None, network=None):
        threading.Thread.__init__(self)
        QObject.__init__(self)
        self.ITEM_LIST = items_list
        self.ITEMS = items
        self.LOCAL = local
        self.NETWORK = network

    def run(self):
        count = 1

        for package in self.ITEM_LIST:
            software_name = "%s - %s" % (package.split("/")[-2], package.split("/")[-1])
            package = package.replace("/", "\\")
            local_dir = package.replace(self.NETWORK, self.LOCAL)

            self.start_signal.emit("\nPackage: %s 开始拷贝>>>>>" % software_name)
            try:
                shutil.copytree(
                    os.path.join(package),
                    os.path.join(local_dir),
                    ignore=shutil.ignore_patterns("*.pyc", "__pycache__")
                )
                self.items_signal.emit([count, u"%s==>>%s 本地化完成" % (package, local_dir)])
            except FileExistsError:
                logger.info("Package本地化失败，文件已存在，请先清理历史文件...")
            count += 1


class LauncherMainWindow(BPyQt5.BQWidget):
    """启动器的主界面框架类"""
    def __init__(self):
        super(LauncherMainWindow, self).__init__()

        # 嵌入更新器
        self.updator = LauncherUpdate()
        install_folder = Updator().get_newest_version().replace("\\", "/")
        python_dir = r"X:/resource/TD/YJpipeline/Tool/Python38/python.exe"
        bat_dir = f"{install_folder}/install.bat"

        if Updator().check_version():  # 到时候把not去掉记得
            self.updator.exec()
            if self.updator.runcode == 1:
                # os.system("start "f"{python_dir} {bat_dir}")
                os.startfile(bat_dir)
                # subprocess.Popen(f"{python_dir} {bat_dir}")
                # os.startfile(install_folder)
                sys.exit(0)

        # ------------------ 记录时间 ------------------ 开始
        total_start_time = datetime.datetime.now()
        # ------------------ 记录时间 ------------------ 结束
        self.setWindowTitle("鹰角网络启动器 v1.0")
        self.setObjectName("MainFrame")
        self.setMinimumSize(900, 500)
        self.setMaximumWidth(900)
        self.setWindowIcon(QIcon("../resource/icon/software_logo.png"))
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.dragable = 0  # 0:不激活拖拽功能 1：激活拖拽功能

        self.app_search = AppSearch()
        self.apps = self.app_search.get_final_apps(types="dcc", ver='single')
        self.build_ui()  # 加载界面

        # 设置QSS样式表
        try:
            root = _config.get("Launcher").get("launcher_local_path")
            BPyQt5.BQssTool.set_qss(f"{root}frame/dcc_launcher.qss", self)
        except:
            root = eval(_config.get("Launcher").get("launcher_local_qss"))
            BPyQt5.BQssTool.set_qss(f"{root}", self)

        # ------------------ 返回信息 ------------------ 开始
        total_end_time = datetime.datetime.now()
        self.main_runtime = (total_end_time - total_start_time).microseconds / 1000000
        logger.info("=" * 30)
        logger.info(" " * 7 + self.windowTitle())
        logger.info("=" * 30)
        logger.info("- 加载UI界面（PyQt5） - 完成 %.2f" % self.ui_runtime)
        logger.info("- 加载启动器 - 完成 %.2f" % self.main_runtime)
        logger.info("-" * 30)
        # ------------------ 返回信息 ------------------ 结束

        self.load_app()  # 加载软件列表

    def build_ui(self):
        """界面构建Func"""
        # ------------------ 记录时间 ------------------ 开始
        start_time = datetime.datetime.now()
        # ------------------ 记录时间 ------------------ 结束

        # 界面主体Layout，QVBOX
        main_layout = BPyQt5.BQVBoxLayout()
        self.setLayout(main_layout)

        # 获取并添加顶部状态栏界面
        status_bar = StatusBar()

        # 创建顶部用于拖拽的透明widget
        drag_widget = BPyQt5.BQPushButton(self)
        drag_widget.setParent(status_bar)
        drag_widget.setFixedSize(827, 60)
        drag_widget.move(70, 3)
        drag_widget.setObjectName("DragWidget")

        # 软件最小化，退出按钮
        btn_min = QPushButton()
        btn_close = QPushButton()
        btn_min.setParent(status_bar)
        btn_close.setParent(status_bar)
        btn_min.setObjectName("MainMinButton")
        btn_close.setObjectName("MainCloseButton")
        btn_min.resize(30, 20)
        btn_close.resize(30, 20)
        btn_min.move(835, 5)
        btn_close.move(865, 5)

        btn_close.clicked.connect(lambda:self.close())
        btn_min.clicked.connect(lambda:self.showMinimized())

        # 创建Tab栏
        buttom_tab_widget = BPyQt5.BQTabWidget()
        buttom_tab_widget.setObjectName("ButtomTab")

        # 创建并添加底部功能界面和子Layout，QHBOX
        buttom_section = BPyQt5.BQWidget()
        buttom_section.setObjectName("ButtomSection")

        # 创建并添加底部控制台界面
        buttom_console = LauncherConsole()
        buttom_console.setObjectName("ButtomConsole")

        # 创建并添加底部设置界面
        self.buttom_setting = LauncherSetting()
        self.buttom_setting.setObjectName("ButtomSetting")

        buttom_tab_widget.addTab(buttom_section, "Software")
        buttom_tab_widget.addTab(buttom_console, "Console")
        buttom_tab_widget.addTab(self.buttom_setting, "Setting")

        # 添加控件至Layout
        main_layout.addWidget(status_bar, 1)
        main_layout.addWidget(buttom_tab_widget, 8)

        # 创建主体界面底部承载的控件的layout
        buttom_layout = BPyQt5.BQHBoxLayout()
        buttom_section.setLayout(buttom_layout)

        # 获取并添加底部功能界面的Application界面
        self.application_section = ApplicationSection()
        self.application_section.btn_refresh.clicked.connect(self.refresh_app)
        self.application_section.btn_localize.clicked.connect(self.localize_app)

        # 创建底部功能区右侧的Widget，并创建Layout，QVBOX
        buttom_right_section = BPyQt5.BQWidget()
        buttom_right_section.setObjectName("ButtomRightSection")

        # 添加控件至Layout
        buttom_layout.addWidget(self.application_section, 7)
        buttom_layout.addWidget(buttom_right_section, 3)

        # 创建底部右侧承载的控件layout
        buttom_right_layout = BPyQt5.BQVBoxLayout()
        buttom_right_section.setLayout(buttom_right_layout)

        # 获取并添加插件选择区
        self.plugin_section = PluginSection()

        # 获取并添加功能区
        self.function_section = FunctionSection()
        self.function_section.startup_btn.clicked.connect(self.app_start)

        # 添加控件至Layout
        buttom_right_layout.addWidget(self.plugin_section, 5)
        buttom_right_layout.addWidget(self.function_section, 5)

        # ------------------ 返回信息 ------------------ 开始
        end_time = datetime.datetime.now()
        self.ui_runtime = (end_time - start_time).microseconds / 1000000
        # ------------------ 返回信息 ------------------ 结束

    def load_app(self):
        """扫描软件包信息，填充界面信息"""
        # ------------------ 记录时间 ------------------ 开始
        start_time_load_app = datetime.datetime.now()
        # ------------------ 记录时间 ------------------ 结束
        logger.info(f"\n开始搜索软件包配置信息：{self.app_search.PATH}")
        logger.info(f"Package Found...\n")
        for app in self.apps:
            item = BPyQt5.BQListWidgetItem()
            item.setSizeHint(QSize(100, 40))

            # 获取软件包中的label和icon
            label = self.app_search.set_label(app)
            icon = self.app_search.set_icon(app)

            # 对label和icon进行设置，并储存app路径以备后用
            item.setText(label)
            item.setIcon(QIcon(icon))
            item.store_app(label, app)

            # 将item添加至list控件中
            self.application_section.app_list.addItem(item)

            # 查询当前app的requires
            requires = self.app_search.get_requires(app)
            logger.info(f"{label[:-15]}")
            logger.info(f"requires：{requires}")

        # 当软件列表点击选择是，发送信号至对应的槽函数，执行界面的局部内容的更新
        self.application_section.app_list.itemClicked.connect(self.load_plugin)
        self.application_section.app_list.itemClicked.connect(self.load_description)
        # ------------------ 返回信息 ------------------ 开始
        end_time_load_app = datetime.datetime.now()
        self.load_app_time = (start_time_load_app - end_time_load_app).microseconds / 1000000
        # ------------------ 返回信息 ------------------ 结束
        logger.info("\n- 软件配置信息搜索 - 完成 %.2f" % self.load_app_time)
        logger.info("="*50 +"\n")

    def load_plugin(self):
        """每次item选择，自动刷新plugin界面UI"""
        # 获取选中的软件对应的app路径
        item = self.application_section.app_list.currentItem()
        text = item.text()
        app = self.application_section.app_list.get_app(item, text)

        # 获取软件包内的插件需求配置，并重置插件显示layout的行数信息
        plugin_info = self.app_search.get_plugins(app)
        self.plugin_section.row = 0

        # 删除之前的控件
        count = self.plugin_section.plugin_layout.count()
        if count:
            for i in range(count):
                self.plugin_section.plugin_layout.itemAt(i).widget().deleteLater()

        # 根据软件包的插件信息创建插件的label 和 version
        for plugin, versions in plugin_info.items():
            self.label = QLabel(plugin)
            self.label.setObjectName("PluginName")
            self.cb = BPyQt5.BQComboBox()
            self.cb.setView(QListView())
            self.cb.setObjectName("PluginVersion")
            for version in versions:
                label = self.app_search.set_version(version)
                self.cb.store_plug(label, version)
                self.cb.addItem(label)
                logger.info(f"{label}")

            # 添加插件信息至layout中
            self.plugin_section.plugin_layout.addWidget(self.label, self.plugin_section.row, 0)
            self.plugin_section.plugin_layout.addWidget(self.cb, self.plugin_section.row, 1)
            self.plugin_section.row += 1

    def load_description(self):
        """每次item选择，自动刷新description界面UI"""
        # 获取选中的软件对应的app路径
        item = self.application_section.app_list.currentItem()
        text = item.text()
        app = self.application_section.app_list.get_app(item, text)

        # 添加软件的描述信息至界面中
        self.function_section.description_content.setPlainText(self.app_search.set_description(app))

    def app_start(self):
        """测试执行软件相关的内容"""
        # ------------------ 记录时间 ------------------ 开始
        start_time_start_app = datetime.datetime.now()
        # ------------------ 记录时间 ------------------ 结束
        # 定义存放软件插件路径的变量，和虚拟环境变量
        env = os.environ.copy()  # 拷贝一份系统环境变量
        load_paths = []
        load_plug = []
        code = 1
        runcode = 0

        try:
            label = self.application_section.app_list.currentItem().text()
            logger.info("="*50)
            logger.info("- 启动软件配置中...\n")
            logger.info(f"软件需求：{label[:-15]}\n")
        except:
            logger.error("请选择需要打开的软件，再执行启动命令")

        # 获取选中的软件对应的app路径
        selected_app = self.application_section.app_list.currentItem()
        if selected_app:
            selected_app = self.application_section.app_list.get_app(selected_app, selected_app.text())
            load_paths.append(selected_app)  # 添加需要读取的软件信息

        # 获取选中的插件版本的路径
        cbs = self.plugin_section.findChildren(QComboBox)  # 获取所有的插件下拉菜单widget
        las = self.plugin_section.findChildren(QLabel)  # 获取所有的插件labe widget
        if las:
            for la in las:
                current_label = la.text()
                load_plug.append(current_label)
            logger.info(f"插件需求：{load_plug}\n")
        if cbs:
            for cb in cbs:
                current_selection = cb.currentText()
                dict_plugins = cb.plug  # 获取储存在菜单中的所有的选项
                plug = dict_plugins.get(current_selection)  # 获取当前选择的插件版本的地址
                load_paths.append(plug)  # 添加到需要读取的软件信息
                if plug:
                    logger.info(f"加载插件：{plug}")
                else:
                    plug = "Plugin Package Not Found..."
                    logger.info(f"加载插件：{plug}")

        if load_paths:
            load_paths = list(filter(None, load_paths))
            # 根据需要加载的插件和软件，读取环境信息并加载
            logger.info(f"\n- Final Package Load Paths：{load_paths}\n")
            for load_path in load_paths:
                if load_path:
                    env, code = self.app_search.create_env(load_path, env, self.function_section.start_mode)  # 根据配置创建软件运行环境
                    runcode = 1
                    if code:
                        logger.error(f"运行环境加载出错ERROR Package: {load_path}")
            if runcode and not code:
                # ------------------ 返回信息 ------------------ 开始
                end_time_start_app = datetime.datetime.now()
                start_app_time = (start_time_start_app - end_time_start_app).microseconds / 1000000
                # ------------------ 返回信息 ------------------ 结束
                logger.info("- 加载软件配置 - 完成 %.2f\n" % start_app_time)
                self.app_search.start_dcc(self.app_search.get_fname(selected_app), env)  # 启动软件
                logger.info("Starting App...\n")
        else:
            logger.error("请选择需要打开的软件，再执行启动命令")

    def closeEvent(self, event):
        """Shuts down application on close."""
        # Return stdout to defaults.
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__
        super().closeEvent(event)

    def refresh_app(self):
        """刷新app界面，同时也应当清除插件和简介界面UI"""
        logger.info("="*50)
        logger.info("重新读取软件信息，刷新界面...")
        # 清空APP列表
        app_count = self.application_section.app_list.count()
        if app_count:
            for i in range(app_count):
                item = self.application_section.app_list.item(0)
                # widget = self.application_section.app_list.itemWidget(item)
                self.application_section.app_list.takeItem(0)
                del item

        # 清空Plugin列表
        plugin_count = self.plugin_section.plugin_layout.count()
        if plugin_count:
            for i in range(plugin_count):
                self.plugin_section.plugin_layout.itemAt(i).widget().deleteLater()

        # 清空Description列表
        self.function_section.description_content.setPlainText(" ")

        # 刷新APP列表
        package_root_current_text = self.buttom_setting.combox_package_source.currentText()
        package_root = self.buttom_setting.combox_package_source.path.get(package_root_current_text)
        if package_root:
            self.app_search.PATH = package_root
            self.app_search.all_apps = self.app_search.get_apps()
            self.apps = self.app_search.get_final_apps(types="dcc", ver='single')
            self.load_app()

    def localize_app(self):
        """本地化软件包和插件包"""
        self.copy_panel = LauncherLocalizePanel()
        self.copy_panel.main()

        self.buttom_setting.combox_package_source.setCurrentIndex(True)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = LauncherMainWindow()
    window.show()
    sys.exit(app.exec_())
